package cn.itcast.nio.c2;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;

/**
 * 演示一下可写事件
 * 写事件服务端
 */
public class WriteServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(8080));
        Selector selector = Selector.open();
        //参数 绑定什么选择器 关注什么事件 附件是什么  附件可以有也可以没有
        //一个selector可以跟多个channel关联
        //不同的channel她们的事件是不同的 所以我们根据事件也可以知道是服务端的channel 还是客户端的channel了
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (true){
            //无事件阻塞 有事件则异步调用将事件放到selectkeys集合中
            selector.select();
            //拿到selectkeys迭代器的每个事件迭代
            Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
            while (iter.hasNext()) {
                //拿出一个事件
                SelectionKey key = iter.next();
                //标识这个key是删除掉的(逻辑删) 让我们下次不会再取到这个key了可以预防空指针
                iter.remove();
                //检查key类型
                if (key.isAcceptable()) {
                    //从服务端中获取客户端的连接
                    SocketChannel sc = ssc.accept();
                    //设置客户端的channel为非阻塞
                    sc.configureBlocking(false);

                    //绑定客户端channel 跟 selector  得到客户端key
                    SelectionKey sckey = sc.register(selector, 0, null);

                    //用服务器端的channel给客户端写数据
                    StringBuilder sb = new StringBuilder();
                    for(int i=0 ; i<30000000; i++){
                        sb.append("a");
                    }
                    ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());

                    //返回实际写入的字节数 向客户端中写入数据
                    int write = sc.write(buffer);
                    System.out.println(write);

                    //判断是否有剩余内容
                    while (buffer.hasRemaining()){
                        //客户端key去关注可写事件
                        //关注了可写事件后只要客户端可写 那么就会自动发一个写事件 所以我们得对写事件进行处理来处理下面注释表明的问题
                        //sckey.interestOps是会覆盖的所以先得到以前的然后跟现在的 + 在一起然后设置回去
                        sckey.interestOps(sckey.interestOps() + SelectionKey.OP_WRITE);
                        //把剩下的没读完的数据存给客户端key让她下次可以继续读
                        //因为做事件区分判断都是用事件Key的所以我们要存给key 然后那边从key中取酒可以了
                        sckey.attach(buffer);
                    }

//                    while (buffer.hasRemaining()){
//                        //返回实际写入的字节数
//                        //我们sb的数据量很大 是不可能一次写进去的 但是这里是死循环代表不管写不写过去 我都让你一直尝试去写 这样很不好
//                        //所以要做一个优化 看上面
//                        int write = sc.write(buffer);
//                        System.out.println(write);
//                    }

                } else if (key.isWritable()){ //如果是写事件
                    //从key中拿出附件
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    //从key中拿出channel  这里的key都是可写来的 所以这里肯定就是
                    SocketChannel sc = (SocketChannel) key.channel();
                    int write = sc.write(buffer);
                    //如果buffer数据写完了就可以删除掉这个buffer附件了
                    if(!buffer.hasRemaining()){
                        key.attach(null);
                        //删除可写事件，这样buffer内容完了后就不会再进入可写事件代码了
                        key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);

                    }
                    System.out.println(write);
                }

            }
        }
    }
}
